package ru.nstu.isma.core.lang.ast.visitor;

import ru.nstu.isma.core.hsm.exp.EXPFunctionOperand;
import ru.nstu.isma.core.lang.ast.LismaBaseVisitor;
import ru.nstu.isma.core.lang.ast.LismaParser;
import ru.nstu.isma.core.hsm.HMEquation;
import ru.nstu.isma.core.hsm.HMVariable;
import ru.nstu.isma.core.hsm.HMVariableTable;
import ru.nstu.isma.core.hsm.exp.EXPOperator;
import ru.nstu.isma.core.hsm.exp.EXPPDEOperand;
import ru.nstu.isma.core.hsm.exp.HMExpression;
import ru.nstu.isma.core.hsm.pde.HMApproximateVariable;
import ru.nstu.isma.core.lang.Infix2PolizConverter;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.ParseTree;

import java.util.HashMap;
import java.util.Map;

/**
 * User: abv
 * Date: 02.12.13
 * Time: 15:33
 */
public class ExpressionVisitor extends LismaBaseVisitor<HMExpression> {

    private static Map<String, EXPOperator> unaryOperators = new HashMap<String, EXPOperator>();

    private static Map<String, EXPOperator> binaryOperators = new HashMap<String, EXPOperator>();

    private HMVariableTable table;

    private HMExpression infix ;

    public ExpressionVisitor(HMVariableTable table) {
        this.table = table;
        initOperators();
    }

    @Override
    public HMExpression visit(@NotNull ParseTree tree) {
        infix = new HMExpression();
        super.visit(tree);
        Infix2PolizConverter converter = new Infix2PolizConverter(infix);
        return converter.convert();
    }

    @Override
    public HMExpression visitPrimary_id(@NotNull LismaParser.Primary_idContext ctx) {
        infix.addOperand(getVariable(ctx.getText()));
        return super.visitPrimary_id(ctx);
    }

    @Override
    public HMExpression visitFunc(@NotNull LismaParser.FuncContext ctx) {
        EXPFunctionOperand func =  new EXPFunctionOperand(ctx.getChild(0).getText());
        LismaParser.Arg_listContext argsCtx = (LismaParser.Arg_listContext) ctx.getChild(2);
        for (int i = 0; i < argsCtx.getChildCount(); i++) {
           if (argsCtx.getChild(i) instanceof LismaParser.ExpressionContext) {
               ExpressionVisitor v = new ExpressionVisitor(table);
               HMExpression ex = v.visit(argsCtx.getChild(i));
               func.addArgExpression(ex);
           }
        }
        infix.add(func);
        return infix;
    }

    @Override
    public HMExpression visitLiteral(@NotNull LismaParser.LiteralContext ctx) {
        double d = Double.valueOf(ctx.getText());
        infix.addUnnamedConst(d);
        return super.visitLiteral(ctx);
    }

    @Override
    public HMExpression visitPartial_operand(@NotNull LismaParser.Partial_operandContext ctx) {

        // 1 достаем уравнение
        String code = ctx.getChild(2).getText();
        HMVariable v = table.get(code);
        if (v == null || !(v instanceof HMEquation)) {
            /// TODO ошибка
        }
        HMEquation eq = (HMEquation) v;

        //2 достаем переменную
        code = ctx.getChild(4).getText();
        v = table.get(code);
        if (v == null || !(v instanceof HMApproximateVariable)) {
            /// TODO ошибка
        }
        HMApproximateVariable apx = (HMApproximateVariable) v;

        // 3 наполняем операнд
        EXPPDEOperand operand = new EXPPDEOperand(eq);
        operand.setApxVar(apx);

        // 4 если указан порядок - проставляем его
        if (ctx.getChild(5).getText().equals(",")) {
            String type = ctx.getChild(6).getText();
            if (type.equals("1")) {
                operand.setOrder(EXPPDEOperand.Order.ONE);
            }
            if (type.equals("2")) {
                operand.setOrder(EXPPDEOperand.Order.TWO);
            }
        }

        infix.add(operand);
        return super.visitPartial_operand(ctx);    //To change body of overridden methods use File | Settings | File Templates.
    }

    // + -  (binary)
    @Override
    public HMExpression visitAdditiveExpressionOperator(@NotNull LismaParser.AdditiveExpressionOperatorContext ctx) {
        addOperator(ctx.getText(), true);
        return super.visitAdditiveExpressionOperator(ctx);
    }

    // * /
    @Override
    public HMExpression visitMultiplicativeExpressionOperator(@NotNull LismaParser.MultiplicativeExpressionOperatorContext ctx) {
        addOperator(ctx.getText(), true);
        return super.visitMultiplicativeExpressionOperator(ctx);
    }

    // not NOT !
    @Override
    public HMExpression visitNot_operator(@NotNull LismaParser.Not_operatorContext ctx) {
        addOperator(ctx.getText(), false);
        return super.visitNot_operator(ctx);    //To change body of overridden methods use File | Settings | File Templates.
    }

    // +  -  (unary)
    @Override
    public HMExpression visitUnaryExpressionOperator(@NotNull LismaParser.UnaryExpressionOperatorContext ctx) {
        addOperator(ctx.getText(), false);
        return super.visitUnaryExpressionOperator(ctx);
    }

    // == !=
    @Override
    public HMExpression visitEqualityExpressionOperator(@NotNull LismaParser.EqualityExpressionOperatorContext ctx) {
        addOperator(ctx.getText(), true);
        return super.visitEqualityExpressionOperator(ctx);
    }

    // < <= > >=
    @Override
    public HMExpression visitRelationalOp(@NotNull LismaParser.RelationalOpContext ctx) {
        addOperator(ctx.getText(), true);
        return super.visitRelationalOp(ctx);
    }

    // or OR ||
    @Override
    public HMExpression visitOr_operator(@NotNull LismaParser.Or_operatorContext ctx) {
        addOperator(ctx.getText(), true);
        return super.visitOr_operator(ctx);
    }

    // and AND &&
    @Override
    public HMExpression visitAnd_operator(@NotNull LismaParser.And_operatorContext ctx) {
        addOperator(ctx.getText(), true);
        return super.visitAnd_operator(ctx);
    }

    // (
    @Override
    public HMExpression visitParExpressionLeftPar(@NotNull LismaParser.ParExpressionLeftParContext ctx) {
        infix.openParenthesis();
        return super.visitParExpressionLeftPar(ctx);
    }

    // )
    @Override
    public HMExpression visitParExpressionRightPar(@NotNull LismaParser.ParExpressionRightParContext ctx) {
        infix.closeParenthesis();
        return super.visitParExpressionRightPar(ctx);
    }

    private void addOperator(String code, boolean binary) {
        EXPOperator o = null;
        if (binary) {
            o = binaryOperators.get(code);
        } else {
            o = unaryOperators.get(code);
        }
        if (o == null) {
            // TODO проверка ошибка аларм
        } else {
            infix.add(o);
        }
    }

    private HMVariable getVariable(String name) {
        HMVariable v = table.get(name);
        if (v == null) {
            // TODO обработать эксепшн
        }
        return v;
    }

    private void initOperators() {
        unaryOperators.put("!", EXPOperator.not());
        unaryOperators.put("not", EXPOperator.not());
        unaryOperators.put("NOT", EXPOperator.not());
        unaryOperators.put("+", EXPOperator.un_plus());
        unaryOperators.put("-", EXPOperator.un_minus());

        binaryOperators.put("+", EXPOperator.add());
        binaryOperators.put("-", EXPOperator.sub());
        binaryOperators.put("*", EXPOperator.mult());
        binaryOperators.put("/", EXPOperator.div());

        binaryOperators.put("<", EXPOperator.less());
        binaryOperators.put(">", EXPOperator.greater());
        binaryOperators.put("==", EXPOperator.equal());
        binaryOperators.put("!=", EXPOperator.not_equal());
        binaryOperators.put("<=", EXPOperator.less_equal());
        binaryOperators.put(">=", EXPOperator.greater_equal());

        binaryOperators.put("and", EXPOperator.and());
        binaryOperators.put("AND", EXPOperator.and());
        binaryOperators.put("&&", EXPOperator.and());

        binaryOperators.put("or", EXPOperator.or());
        binaryOperators.put("OR", EXPOperator.or());
        binaryOperators.put("||", EXPOperator.or());
    }
}
